//////////////////////////////////////////////////////////////////////////
#pragma once


//////////////////////////////////////////////////////////////////////////
#include "Math/Math.hpp"
#include "Math/Simd/Simd.hpp"


namespace Math
{
	// NOTE: Since this is basically nothing but a light wrapper around the
	// SIMD abstraction layer, we just inline everything here and don't
	// bother splitting things up.  This will change as debugging is added
	// but for right now, keeping things compact and easy to edit is more
	// important than a nice neat file.
	template< typename Simd=Simd_t >
	class BEGIN_ALIGNED( Vector3f_Simd, 16 )
	{
	public:
		// Pull the type from the base Simd definition.
		typedef Simd													SimdType_t;
		typedef typename Simd::F32x4_t									ValueType_t;
		typedef typename Math::Simd::Param< ValueType_t >::Type_t		ParamType_t;

		// Element index constants.
		static const size_t						kXIndex		=	0;
		static const size_t						kYIndex		=	1;
		static const size_t						kZIndex		=	2;

		// Construction.  NOTE: NOT default initialized to zero!
		Vector3f_Simd()																	{}
		Vector3f_Simd( const Vector3f_Simd& rhs ) : mData( rhs.mData )					{}
		Vector3f_Simd( float x, float y, float z ) : mData( Simd::Create( x, y, z ) )	{}

		// Constructor/converter pair for passing as SIMD::Vec4f_t so it can be
		// passed as a register.  These are non-explicit in order to 'encourage'
		// use as it is better than references and such in this specific case.
		Vector3f_Simd( ParamType_t rhs ): mData( rhs )									{}
		operator ParamType_t() const													{return mData;}

		// Element access.
		float						X() const											{return Simd::Fetch( mData, 0 );}
		float						Y() const											{return Simd::Fetch( mData, 1 );}
		float						Z() const											{return Simd::Fetch( mData, 2 );}

		void						X( float x )										{mData = Simd::Store( mData, x, 0 );}
		void						Y( float y )										{mData = Simd::Store( mData, y, 1 );}
		void						Z( float z )										{mData = Simd::Store( mData, z, 2 );}

		// Element array like access.  NOTE: Using the () operator and not [] since
		// we can't return a reference with the SIMD version so v[ 1 ] = 1.0f is
		// an invalid construct.  It "can" be done with a bit of extra work but
		// is abusing the operator in this way better or is the extra work/complexity better?
		// Could of course use the array indexer for one and the command for the other?
		// Or possibly just use a named function.
		float						operator ()( size_t index ) const					{return Simd::Fetch( mData, index );}
		void						operator ()( size_t index, float v )				{mData = Simd::Store( mData, v, index );}

		// Explicit set operation.  Syntactic sugar for X(x); Y(y); Z(z), also a bit faster.
		void						Set( float x, float y, float z )					{mData = Simd::Create( x, y, z );}

		// Assignment.
		const Vector3f_Simd&		operator =( const Vector3f_Simd& v )				{mData = v.mData; return *this;}
		const Vector3f_Simd&		operator =( ParamType_t v )							{mData = v; return *this;}

		// Math operators.
		Vector3f_Simd				operator -() const									{return Simd::Negate( mData );}
		Vector3f_Simd				operator +( const Vector3f_Simd& rhs ) const		{return Simd::Add( mData, rhs.mData );}
		Vector3f_Simd				operator -( const Vector3f_Simd& rhs ) const		{return Simd::Sub( mData, rhs.mData );}
		Vector3f_Simd				operator *( float rhs ) const						{return Simd::Mul( mData, Simd::Create( rhs, rhs, rhs ) );}
		Vector3f_Simd				operator /( float rhs ) const						{return Simd::Div( mData, Simd::Create( rhs, rhs, rhs, 1.0f ) );}
		float						operator *( const Vector3f_Simd& rhs ) const		{return Simd::Dot3( mData, rhs.mData );}

		// Assignment math operators.
		void						operator -=( const Vector3f_Simd& rhs )				{mData = Simd::Sub( mData, rhs.mData );}
		void						operator +=( const Vector3f_Simd& rhs )				{mData = Simd::Add( mData, rhs.mData );}
		void						operator *=( const float rhs )						{mData = Simd::Mul( mData, Simd::Create( rhs, rhs, rhs ) );}
		void						operator /=( const float rhs )						{mData = Simd::Div( mData, Simd::Create( rhs, rhs, rhs, 1.0f ) );}

		// Member operations.
		void						Normalize()											{mData = Simd::Norm3( mData );}
		void						Cross( const Vector3f_Simd& rhs )					{mData = Simd::Cross3( mData, rhs );}
		bool						IsValid() const										{return Simd::IsValid( mData );}

		// Static initializers.
		static const Vector3f_Simd	Zero()												{return Vector3f_Simd( 0.0f, 0.0f, 0.0f );}
		static const Vector3f_Simd	XAxis()												{return Vector3f_Simd( 1.0f, 0.0f, 0.0f );}
		static const Vector3f_Simd	YAxis()												{return Vector3f_Simd( 0.0f, 1.0f, 0.0f );}
		static const Vector3f_Simd	ZAxis()												{return Vector3f_Simd( 0.0f, 0.0f, 1.0f );}

	private:
		// Implementation data.
		ValueType_t					mData;
	} END_ALIGNED( Vector3f_Simd, 16 );


	//////////////////////////////////////////////////////////////////////////
	// Typedef the default selected level of Simd in use.
	typedef Vector3f_Simd< Simd_t >	Vector3fv;


	//////////////////////////////////////////////////////////////////////////
	// Helper operator.
	template< typename Type >
	inline Vector3f_Simd< Type >	operator *( float lhs, const Vector3f_Simd< Type >& rhs )
	{
		return rhs * lhs;
	}


	//////////////////////////////////////////////////////////////////////////
	// Helper functions.
	template< typename Type >
	inline float					Dot( const Vector3f_Simd< Type >& lhs, const Vector3f_Simd< Type >& rhs )
	{
		return lhs * rhs;
	}

	template< typename Type >
	inline Vector3f_Simd< Type >	Normalize( const Vector3f_Simd< Type >& rhs )
	{
		Vector3f_Simd< Type >	result( rhs );
		result.Normalize();
		return result;
	}

	template< typename Type >
	inline Vector3f_Simd< Type >	Cross( const Vector3f_Simd< Type >& lhs, const Vector3f_Simd< Type >& rhs )
	{
		Vector3f_Simd< Type >	result( lhs );
		result.Cross( rhs );
		return result;
	}

	template< typename Type >
	inline float					Magnitude( const Vector3f_Simd< Type >& lhs )
	{
		return Type::Mag3( lhs );
	}

	template< typename Type >
	inline float					MagnitudeSquared( const Vector3f_Simd< Type >& lhs )
	{
		return lhs*lhs;
	}

	template< typename Type >
	inline Vector3f_Simd< Type >	Reflect( const Vector3f_Simd< Type >& v, const Vector3f_Simd< Type >& axis )
	{
		float	dot2	= axis * v * 2.0f;
		return( dot2*axis-v );
	}

	template< typename Type >
	inline Vector3f_Simd< Type >	Refract( const Vector3f_Simd< Type >& i, const Vector3f_Simd< Type >& n, float eta )
	{
		float					cosI	= Dot( -n, i );
		float					sinT2	= eta*eta * (1.0f - cosI * cosI);
		if( sinT2 > 1.0f )
			return Vector3f_Simd< Type >::Zero();
		return eta*i - (eta + std::sqrt( 1.0f - sinT2 )) * -n;
	}
}
